package io.github.jianjianghui.groot.spring;


import cn.hutool.core.thread.ThreadUtil;
import io.github.jianjianghui.groot.core.*;
import io.github.jianjianghui.groot.core.annotation.ContentFormatValidator;
import io.github.jianjianghui.groot.core.annotation.GrootController;
import io.github.jianjianghui.groot.core.annotation.GrootMemoryUnit;
import io.github.jianjianghui.groot.core.annotation.Idempotent;
import io.github.jianjianghui.groot.spring.util.HttpRequest;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;

/**
 * 幂等拦截器
 *
 * @author <a href="https://www.github.com/jianjianghui">Jianghui Jian<a/>
 * @date 2021/8/27 - 13:39
 */
@Aspect
public class SpringGrootController implements GrootController, Ordered {
    final private static Logger log = LoggerFactory.getLogger(SpringGrootController.class);

    public static int GROOT_ORDER = Integer.MAX_VALUE;

    final private ContentFormatValidator contentFormatValidator;
    final private GrootValidator grootValidator;
    final private GrootMemoryUnit grootMemoryUnit;
    final private GrootConfig grootConfig;

    public SpringGrootController(ContentFormatValidator contentFormatValidator, GrootValidator grootValidator, GrootMemoryUnit grootMemoryUnit, GrootConfig grootConfig) {
        this.contentFormatValidator = contentFormatValidator;
        this.grootValidator = grootValidator;
        this.grootMemoryUnit = grootMemoryUnit;
        this.grootConfig = grootConfig;
    }

    @Pointcut("@annotation(idempotent)")
    public void pointcut(Idempotent idempotent) {
    }


    @Override
    @Before(value = "pointcut(idempotent)", argNames = "idempotent")
    public void before(Idempotent idempotent) {
        log.trace("groot before");
        HttpRequest httpRequest = HttpRequest.currentRequest();
        if (checkRequest(httpRequest)) {
            return;
        }

        String requestId = httpRequest.sourceRequest().getHeader(grootConfig.getHeaderTag());
        if (StringUtils.isBlank(requestId)) {
            throw new GrootException(401, grootConfig.getHeaderTagNotFound());
        }

        if (contentFormatValidator.valid(requestId)) {
            throw new GrootException(400, grootConfig.getHeaderTagIllegalContent());
        }

        Groot groot = Groot.create(idempotent,
                GrootRequest.create(httpRequest.ip(), requestId, contentFormatValidator));
        GrootRequest grootRequest = groot.getGrootRequest();
        if (grootValidator.valid(groot)) {
            throw new GrootException(403, grootConfig.getHeaderTagVerificationFailed());
        }

        grootRequest.setStatusEnum(GrootStatusEnum.PREP);
        ThreadUtil.execute(() -> grootMemoryUnit.add(groot));
    }

    @Override
    @AfterReturning(value = "pointcut(idempotent)", argNames = "idempotent")
    public void afterReturning(Idempotent idempotent) {
        log.trace("groot afterReturning");
        HttpRequest httpRequest = HttpRequest.currentRequest();
        if (checkRequest(httpRequest)) {
            return;
        }

        Groot groot = Groot.create(idempotent, GrootRequest.create(httpRequest.ip(),
                httpRequest.sourceRequest().getHeader(grootConfig.getHeaderTag()), contentFormatValidator));
        GrootRequest grootRequest = groot.getGrootRequest();
        grootRequest.setStatusEnum(GrootStatusEnum.OK);

        grootMemoryUnit.add(groot);
    }

    @Override
    @AfterThrowing(value = "pointcut(idempotent)", argNames = "idempotent")
    public void afterThrowing(Idempotent idempotent) {
        log.trace("groot afterThrowing");
        HttpRequest httpRequest = HttpRequest.currentRequest();
        if (checkRequest(httpRequest)) {
            return;
        }

        Groot groot = Groot.create(idempotent, GrootRequest.create(httpRequest.ip(),
                httpRequest.sourceRequest().getHeader(grootConfig.getHeaderTag()), contentFormatValidator));
        GrootRequest grootRequest = groot.getGrootRequest();
        grootRequest.setStatusEnum(GrootStatusEnum.ERROR);

        grootMemoryUnit.add(groot);
    }


    private boolean checkRequest(HttpRequest httpRequest) {
        return httpRequest.isNotAvailableRequest();
    }


    @Override
    public int getOrder() {
        return GROOT_ORDER;
    }
}
